package control.reader;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


import data.base.TextureCoordinate;
import data.base.Vector3;
import data.base.Vertex;
import data.complex.Triangle;
import data.hierarchy.Mesh;
import data.hierarchy.TriangleGroup;


public class ObjReader {  
  private List<Vector3> points;
  private List<TextureCoordinate> textureCoordinates;
  private List<Vector3> normals;
  private Mesh mesh;
  private TriangleGroup currentGroup;
  private Map<Integer, TriangleGroup> smoothingGroups;
  private Integer currentSmoothingGroup = null;
  private MtlReader materialLib;
  
  private static final String POINT = "v";
  private static final String VERTEX_NORMALS = "vn";
  private static final String TEXTURE_COORDINATE = "vt";
  private static final String FACE = "f";
  private static final String GROUP = "g";
  private static final String SMOOTHING = "s";
  private static final String SMOOTHING_OFF = "off";
  private static final String MATERIAL_FILE = "mtllib";
  private static final String MATERIAL = "usemtl";
  
  public ObjReader(){
    
  }
    
  public Mesh readFile(String fileName){
    mesh = new Mesh();
    points = new ArrayList<Vector3>();
    textureCoordinates = new ArrayList<TextureCoordinate>();
    normals = new ArrayList<Vector3>();
    smoothingGroups = new HashMap<Integer, TriangleGroup>();
    try {
      FileInputStream fis = new FileInputStream(fileName);
      InputStreamReader isr = new InputStreamReader(fis);
      BufferedReader br = new BufferedReader(isr);
      String line;
      while((line = br.readLine()) != null){
        interpreteLineTokens(line.split(" "));
      }
      processTriangleGroup();
      for(TriangleGroup sg : smoothingGroups.values()){
        sg.generateSmoothedVertexNormals();
      }
    }
    catch (Exception e) {
      e.printStackTrace();
    }    
    return mesh;
  }
  
  private void processTriangleGroup(){
    if(currentGroup != null)
      mesh.addTriangleGroup(currentGroup);
    currentGroup = new TriangleGroup();
  }
  
  private TriangleGroup getTriangleGroup(){
    if(currentGroup == null)
      currentGroup = new TriangleGroup();
    return currentGroup;
  }
  
  private void interpreteLineTokens(String[] tokens){
    String command = tokens[0];
    
    if(command.equals(POINT))
      readPoint(tokens);
    else if(command.equals(VERTEX_NORMALS))
      readVertexNormals(tokens);
    else if(command.equals(TEXTURE_COORDINATE))
      readTextureCoordinates(tokens);
    else if(command.equals(FACE))
      readFace(tokens);
    else if(command.equals(GROUP))
      readGroup(tokens);
    else if(command.equals(SMOOTHING))
      readSmoothing(tokens);
    else if(command.equals(MATERIAL_FILE))
      materialLib = new MtlReader(tokens[1]);
    else if(command.equals(MATERIAL))
      getTriangleGroup().setMaterial(materialLib.getMaterial(tokens[1]));
  }
  
  private void readPoint(String[] tokens){
    points.add(new Vector3(
        Double.parseDouble(tokens[1]),
        Double.parseDouble(tokens[2]),
        Double.parseDouble(tokens[3])));
  }
  
  private void readVertexNormals(String[] tokens){
    normals.add(new Vector3(
        Double.parseDouble(tokens[1]),
        Double.parseDouble(tokens[2]),
        Double.parseDouble(tokens[3])));
  }
  
  private void readTextureCoordinates(String[] tokens){
    textureCoordinates.add(new TextureCoordinate(
        Double.parseDouble(tokens[1]),
        Double.parseDouble(tokens[2])));
  }
  
  private void readGroup(String[] tokens){
    processTriangleGroup();
    for(int i = 1; i < tokens.length; i++)
      currentGroup.getTags().add(tokens[i]);
  }
  
  private void readSmoothing(String[] tokens){
    if(tokens[1].toLowerCase().equals(SMOOTHING_OFF)){
      currentSmoothingGroup = null;
    }
    else{
      currentSmoothingGroup = Integer.parseInt(tokens[1]);
    }
  }
  
  private void readFace(String[] tokens){
    Vertex firstVertex = getVertex(tokens[1]);
    
    for(int i = 3; i < tokens.length; i++){
      Triangle t = new Triangle(
          firstVertex,
          getVertex(tokens[i - 1]),
          getVertex(tokens[i]));
      getTriangleGroup().addTriangle(t, true);
      addTriangleToSmoothingGroup(t);
    }
  }
  
  private Vertex getVertex(String vString){
    String[] vTokens = vString.split("/");
    Vertex v = new Vertex(points.get(Integer.parseInt(vTokens[0]) - 1));
    if(vTokens.length > 1 && !vTokens[1].equals(""))
      v.setTextureCoordinate(textureCoordinates.get(Integer.parseInt(vTokens[1]) - 1));
    if(vTokens.length > 2 && !vTokens[2].equals(""))
      v.setNormal(normals.get(Integer.parseInt(vTokens[2]) - 1));
    return v;
  }
  
  private void addTriangleToSmoothingGroup(Triangle t){
    if(currentSmoothingGroup != null){
      if(smoothingGroups.get(currentSmoothingGroup) == null){
        smoothingGroups.put(currentSmoothingGroup, new TriangleGroup());
      }
      smoothingGroups.get(currentSmoothingGroup).addTriangle(t, false);
    }
  }
}